/*
 * Team Project 1: Database Implementation and Use
 * CPSC 315
 *
 * Class: Condition
 *
 * The Condition class parses and evaluates the WHERE portion of the query
 * statement, if that portion is a logic operation.
 *
 * SYNTAX FOR TOKEN PARSING
 *
 * The string after the WHERE statement, if not IN or EXISTS, should be of one of
 * the following forms:
 *
 * operation: (A op B)
 *
 * not: (NOT(A op B))
 *
 * and: (A op B)AND(C op D)
 *
 * or: (A op B)OR(C op D)
 *
 * multiple: (NOT(A op B))AND(C op D)
 *           (A op B)AND(NOT(C op D))
 *           (A op B)AND(C op D)OR(E op F)
 *
 * More combinations are possible as long as they follow this basic idea.
 * This will ensure that statement is evaluated correctly.
 *
 * @author Justin Kern, Jill Greczek
 * */

import java.util.Vector;

public class Condition {
    
    //EXCEPTIONS
    Exception NotComparableException = new Exception("This type is not comparable to a String");
    Exception InvalidOperationException = new Exception("This is an invalid operator");
    Exception UnidentifiedOperandException = new Exception("Unidentified opperand");
    Exception NotSolvedException = new Exception("Expression not fully solved");
    Exception NoSuchColumnException = new Exception("Column Does not exist.");
    
    //DATA MEMBERS
    private String[] tokens;
    private Relation rdata = new Relation();
    private Vector <Relation> solvedList = new Vector();
    private Vector <String> binaryOperators = new Vector();
    
    //CONSTRUCTORS
    public Condition(String s, Relation r){
        //create a new condition given a relation and a string of conditions
        //r is copied into rdata
        //s is parsed into tokens
        Vector columnNames = new Vector();
        for (int i = 0; i < r.getColumnCount(); i++){
            columnNames.add(r.getColumnName(i));
        }
        rdata = new Relation(r.relationName(), r.getDataVector(), columnNames);
        String delims = "[()]";
        tokens = s.split(delims);
    }
       
    
    //METHODS
    public Relation solve(String l, String o, String r) throws Exception{
        //returns the modified Relation from a basic unit of the parsed condition
        //this unit is of the form (A op B)
        //AND, NOT, and OR are handled in binarySolve()
        int leftColumnIndex = 0;
        Number rightNumber = 0;
        String rightString = "";
        boolean rightIsInt = false; //True when the right is an integer value and False otherwise
        boolean rightIsFloat = false;
        

        if (rdata.isColumnName(l)){
            leftColumnIndex = rdata.getColumnIndex(l);
        }
        else{
            throw NoSuchColumnException;
        }
        
        //The right value will either be a string or an integer
        //each case must be handled differently
        if(Character.isDigit(r.charAt(0))){
            if (r.contains(".")){
                rightNumber =  Float.parseFloat(r);
                rightIsFloat = true;
                rightIsInt = false;
            }
            else{
                rightNumber = Integer.parseInt(r);
                rightIsInt = true;
                rightIsFloat = false;
            }
        }
        else{
            rightString = r;
            rightIsInt = false;
            rightIsFloat = false;
        }
        
        if(o.equals("=")){
            Vector rdataColumnNames = new Vector(); //copy of column names
            for(int i = 0; i < rdata.getColumnCount(); i++){
                rdataColumnNames.add(rdata.getColumnName(i));
            }   
            Relation answer = new Relation(rdata.relationName(), new Vector(), rdataColumnNames);
            
            if (rightIsInt && !rightIsFloat){
                //if the right is an integer, go through each element of the
                //specified column and remove rows where rightNumber is not
                //equal to the specified element
                
                for(int i = 0; i < rdata.getRowCount(); i++){
                    if (rightNumber.intValue() == (Integer.parseInt(rdata.getElement(i, leftColumnIndex).toString()))){
                        answer.insertRow(0, rdata.getRow(i));
                    }
                }
            }
            
            else if (!rightIsInt && rightIsFloat){
                //if the right is an integer, go through each element of the
                //specified column and remove rows where rightNumber is not
                //equal to the specified element
                
                for(int i = 0; i < rdata.getRowCount(); i++){
                    if (rightNumber.floatValue() == (Float.parseFloat(rdata.getElement(i, leftColumnIndex).toString()))){
                        answer.insertRow(0, rdata.getRow(i));
                    }
                }
            }
            
            else{
                //if the right is a string, go through each element of the
                //specified column and remove rows where rightString is not
                //equal to the specified element
                
                for(int i = 0; i < rdata.getRowCount(); i++){ 
                    if(rightString.equals(rdata.getElement(i, leftColumnIndex).toString())){
                        answer.insertRow(0, rdata.getRow(i));
                    }
                }
            
            }
            return answer;
        }
        
        //****End of '='
        
        if(o.equals("!=")){
            Vector rdataColumnNames = new Vector(); //copy of column names
            for(int i = 0; i < rdata.getColumnCount(); i++){
                rdataColumnNames.add(rdata.getColumnName(i));
            }   
            Relation answer = new Relation(rdata.relationName(), new Vector(), rdataColumnNames);

            if (rightIsInt && !rightIsFloat){
                //if the right is an integer, go through each element of the
                //specified column and remove rows where rightNumber is
                //equal to the specified element
                for(int i = 0; i < rdata.getRowCount(); i++){
                    if (rightNumber.intValue() != (Integer.parseInt(rdata.getElement(i, leftColumnIndex).toString()))){
                        answer.insertRow(0, rdata.getRow(i));
                    }
                }
            }
            
            else if (!rightIsInt && rightIsFloat){
                //if the right is an integer, go through each element of the
                //specified column and remove rows where rightNumber is
                //equal to the specified element
                for(int i = 0; i < rdata.getRowCount(); i++){
                    if (rightNumber.floatValue() != (Float.parseFloat(rdata.getElement(i, leftColumnIndex).toString()))){
                        answer.insertRow(0, rdata.getRow(i));
                    }
                }
            }
            else{
                //if the right is a string, go through each element of the
                //specified column and remove rows where rightString is
                //equal to the specified element
                for(int i = 0; i < rdata.getRowCount(); i++){ 
                    if(!rightString.equals(rdata.getElement(i, leftColumnIndex).toString())){
                        answer.insertRow(0, rdata.getRow(i));
                    }
                }
            }
            return answer;
        }
        
        //****End of '!='
        
        if (o.equals(">")){
            try{
                if (rightIsInt && !rightIsFloat){
                    //if the right is an integer, go through each element of the
                    //specified column and remove rows where rightNumber is
                    //less than or equal to the specified element
                    Vector rdataColumnNames = new Vector(); //copy of column names
                    for(int i = 0; i < rdata.getColumnCount(); i++){
                        rdataColumnNames.add(rdata.getColumnName(i));
                    }   
                    Relation answer = new Relation(rdata.relationName(), new Vector(), rdataColumnNames);
                    
                    for(int i = 0; i < rdata.getRowCount(); i++){
                        if (rightNumber.intValue() < (Integer.parseInt(rdata.getElement(i, leftColumnIndex).toString()))){
                            answer.insertRow(0, rdata.getRow(i));
                        }
                    }
                    return answer;
                }
                
                else if (!rightIsInt && rightIsFloat){
                    //if the right is an integer, go through each element of the
                    //specified column and remove rows where rightNumber is
                    //less than or equal to the specified element
                    Vector rdataColumnNames = new Vector(); //copy of column names
                    for(int i = 0; i < rdata.getColumnCount(); i++){
                        rdataColumnNames.add(rdata.getColumnName(i));
                    }   
                    Relation answer = new Relation(rdata.relationName(), new Vector(), rdataColumnNames);
                    
                    for(int i = 0; i < rdata.getRowCount(); i++){
                        if (rightNumber.floatValue() < (Float.parseFloat(rdata.getElement(i, leftColumnIndex).toString()))){
                            answer.insertRow(0, rdata.getRow(i));
                        }
                    }
                    return answer;
                }
                else{
                    //if the right is a string, throw an exception
                   throw NotComparableException;
                }
            }
            catch(Exception e){
                System.out.println(e.getMessage());
            }
                 
        }
        
        //****End of  '>'
        
        if (o.equals("<")){
            try{
                if (rightIsInt && !rightIsFloat){
                    //if the right is an integer, go through each element of the
                    //specified column and remove rows where rightNumber is
                    //greater than or equal to the specified element
                    Vector rdataColumnNames = new Vector(); //copy of column names
                    for(int i = 0; i < rdata.getColumnCount(); i++){
                        rdataColumnNames.add(rdata.getColumnName(i));
                    }   
                    Relation answer = new Relation(rdata.relationName(), new Vector(), rdataColumnNames);
                    
                    for(int i = 0; i < rdata.getRowCount(); i++){
                        if (rightNumber.intValue() > (Integer.parseInt(rdata.getElement(i, leftColumnIndex).toString()))){
                                answer.insertRow(0, rdata.getRow(i));
                        }
                    }
                    return answer;
                }
                else if (!rightIsInt && rightIsFloat){
                    //if the right is an integer, go through each element of the
                    //specified column and remove rows where rightNumber is
                    //greater than or equal to the specified element
                    Vector rdataColumnNames = new Vector(); //copy of column names
                    for(int i = 0; i < rdata.getColumnCount(); i++){
                        rdataColumnNames.add(rdata.getColumnName(i));
                    }   
                    Relation answer = new Relation(rdata.relationName(), new Vector(), rdataColumnNames);
                    
                    for(int i = 0; i < rdata.getRowCount(); i++){
                        if (rightNumber.floatValue() > (Float.parseFloat(rdata.getElement(i, leftColumnIndex).toString()))){
                                answer.insertRow(0, rdata.getRow(i));
                        }
                    }
                    return answer;
                }
                else{
                    //if the right is a string, throw an exception
                    throw NotComparableException;
                }
            }
            catch(Exception e){
                System.out.println(e.getMessage());
            }
                 
        }
        
        //****End of  '<'
        
        if (o.equals(">=")){
            try{
                if (rightIsInt && !rightIsFloat){
                    //if the right is an integer, go through each element of the
                    //specified column and remove rows where rightNumber is
                    //less than the specified element
                    Vector rdataColumnNames = new Vector(); //copy of column names
                    for(int i = 0; i < rdata.getColumnCount(); i++){
                        rdataColumnNames.add(rdata.getColumnName(i));
                    }   
                    Relation answer = new Relation(rdata.relationName(), new Vector(), rdataColumnNames);
                    
                    for(int i = 0; i < rdata.getRowCount(); i++){
                        if (rightNumber.intValue() <= (Integer.parseInt(rdata.getElement(i, leftColumnIndex).toString()))){
                            answer.insertRow(0, rdata.getRow(i));
                        }
                    }
                    return answer;
                }
                else if (!rightIsInt && rightIsFloat){
                    //if the right is an integer, go through each element of the
                    //specified column and remove rows where rightNumber is
                    //less than the specified element
                    Vector rdataColumnNames = new Vector(); //copy of column names
                    for(int i = 0; i < rdata.getColumnCount(); i++){
                        rdataColumnNames.add(rdata.getColumnName(i));
                    }   
                    Relation answer = new Relation(rdata.relationName(), new Vector(), rdataColumnNames);
                    
                    for(int i = 0; i < rdata.getRowCount(); i++){
                        if (rightNumber.floatValue() <= (Float.parseFloat(rdata.getElement(i, leftColumnIndex).toString()))){
                            answer.insertRow(0, rdata.getRow(i));
                        }
                    }
                    return answer;
                }
                else{
                    //if the right is a string, throw an exception
                    throw NotComparableException;
                }
            }
            catch(Exception e){
                System.out.println(e.getMessage());
            }
                 
        }
        
        //****End of  '>='
        
        if (o.equals("<=")){
            try{
                if (rightIsInt && !rightIsFloat){
                    //if the right is an integer, go through each element of the
                    //specified column and remove rows where rightNumber is
                    //greater than the specified element
                    Vector rdataColumnNames = new Vector(); //copy of column names
                    for(int i = 0; i < rdata.getColumnCount(); i++){
                        rdataColumnNames.add(rdata.getColumnName(i));
                    }   
                    Relation answer = new Relation(rdata.relationName(), new Vector(), rdataColumnNames);
                    
                    for(int i = 0; i < rdata.getRowCount(); i++){
                        if (rightNumber.intValue() >= (Integer.parseInt(rdata.getElement(i, leftColumnIndex).toString()))){
                            answer.insertRow(0, rdata.getRow(i));
                        }
                    }
                    return answer;
                }
                else if (!rightIsInt && rightIsFloat){
                    //if the right is an integer, go through each element of the
                    //specified column and remove rows where rightNumber is
                    //greater than the specified element
                    Vector rdataColumnNames = new Vector(); //copy of column names
                    for(int i = 0; i < rdata.getColumnCount(); i++){
                        rdataColumnNames.add(rdata.getColumnName(i));
                    }   
                    Relation answer = new Relation(rdata.relationName(), new Vector(), rdataColumnNames);
                    
                    for(int i = 0; i < rdata.getRowCount(); i++){
                        if (rightNumber.floatValue() >= (Float.parseFloat(rdata.getElement(i, leftColumnIndex).toString()))){
                            answer.insertRow(0, rdata.getRow(i));
                        }
                    }
                    return answer;
                }
                else{
                    //if the right is a string, throw an exception
                    throw NotComparableException;
                }
            }
            catch(Exception e){
                System.out.println(e.getMessage());
            }
                 
        }
        
        //****End of  '<='
            
        return rdata;
    }
    
    public Relation binarySolve(Relation left, String op, Relation right){
        //returns a Relation modified by conditions AND or OR
        try{
            if (op.equals("OR")){
                //return relation of elements in left OR right
                //add all elements from 'right' to 'left' except duplicate tuples
                for (int i = 0; i < right.getRowCount(); i++){
                    if (!left.isElement(right.getElement(i, 0))){
                        left.addRow((Vector)right.getDataVector().elementAt(i));
                    }
                }
                return left;
            }

            else if (op.equals("AND")){
                //return relation of elements in left AND right
                Vector rdataColumnNames = new Vector(); //copy of column names
                for(int i = 0; i < rdata.getColumnCount(); i++){
                    rdataColumnNames.add(rdata.getColumnName(i));
                }   
                Relation answer = new Relation(rdata.relationName(), new Vector(), rdataColumnNames);
                
                //add to 'answer' all elements that exist in left and right
                for (int i = 0; i < left.getRowCount(); i++){
                    if (!right.isElement(left.getElement(i, 0))){
                        answer.insertRow(0, right.getRow(i));
                    }
                }
                return answer;
            }
            else{
                throw InvalidOperationException;
                
            }
        }
        catch(Exception e){
            System.out.println(e.getMessage());
        }
        return null; 
    }
    
    public Relation not(Relation r){
        //removes relation r from relation answer leaving the negation of r
        Vector rdataColumnNames = new Vector(); //copy of column names
        for(int i = 0; i < rdata.getColumnCount(); i++){
            rdataColumnNames.add(rdata.getColumnName(i));
        }   
        Relation answer = new Relation(rdata.relationName(), new Vector(), rdataColumnNames);        
        //for every element in r, if it doesn't exists in rdata, add it to answer
        for(int i = 0; i < rdata.getRowCount(); i++){
            if(!r.isElement(rdata.getElement(i, 0))){
                answer.insertRow(0, rdata.getRow(i));
            }
        }
        return answer;
    }
    
    public void solveAll() throws Exception {
        //modifieds solvedList
        //if successful, solvedList should have one element remaining, the final
        //modified relation

        //go through each token and evaluate it
        for(int i = 1; i<tokens.length; i++){
            String thisToken = tokens[i];
            //because of how split works, if NOT is the first token passed,
            //(ex. "(NOT(A < B))"
            //there will be a "" passed after that token is evaluated.
            //this statement compensates for that.
            if(thisToken.equals("")){
                System.out.println("You put NOT as the first token, didn't you!?!1?!?!");
            }
            //for all other cases, NOT is parsed normally
            else if (thisToken.equals("AND") || thisToken.equals("OR") || thisToken.equals("NOT")){
                binaryOperators.add(thisToken);
            }
            //the final calls solve to evaluate a token of the form (A op B)
            else{   
                String delims2 = "[ ]";
                String[] evalTokens = thisToken.split(delims2);
                if(evalTokens.length>2){
                        String left = evalTokens[0];
                        String op = evalTokens[1];
                        String right = evalTokens[2];
                        Relation solution = solve(left, op, right);
                        if (solution != null){
                            solvedList.add(solution);
                        }
                        else{
                            throw InvalidOperationException;
                        } 
                }else{
                    //if the token is parsed into less than three tokens,
                    //there was an incorrect parse
                    throw UnidentifiedOperandException;
                }
            }
        }
        //special case if the first operator is "NOT"
        //negate the first element of "solvedList"
        if (binaryOperators.size()>0){
            if (binaryOperators.elementAt(0).equals("NOT")){
                Relation element = solvedList.firstElement();
                element = not(element);
                solvedList.setElementAt(element, 0);
                binaryOperators.remove(0);
            }
        }
        
        //solve the remainder of the condition:
        //iterate through solvedList and combine elements as specified by
        //the corresponding element in binaryOperators
        while(binaryOperators.size() > 0){
            Relation left = solvedList.elementAt(0);
            Relation right = solvedList.elementAt(1);
            
            if (binaryOperators.size()> 1){
                if (binaryOperators.elementAt(1).equals("NOT")){
                    right = not(right);
                    binaryOperators.remove(1);
                }
            }
            //add the solution to solvedList
            Relation solution = binarySolve(left, binaryOperators.elementAt(0), right);
            solvedList.set(0, solution);
            solvedList.remove(1);
            
            binaryOperators.remove(0);
        }
        if(solvedList.size()>1){
            throw NotSolvedException;
        }
    }
    
    public Relation getAnswer() throws Exception{
        //returns the solution via private member solvedList
        solveAll();
        return solvedList.firstElement();
    }
}





    

    
   

